﻿///////////////////////////////////////////////////////////////////////////////
// Copyright 2016 lingzimaike@gmail.com
// GPLv2
// Author: lingzimaike
///////////////////////////////////////////////////////////////////////////////

#include "papa/net/tcp_socket.h"
#include <cassert>
#include <event2/buffer.h>
#include "papa/net/socket_event_handler.h"
#include "papa/net/tcp_server.h"

namespace papa {
namespace net {

	TcpSocket::TcpSocket(SocketEventHandler* handler)
		: id_(0)
		, sock_(-1)
		, bev_(NULL)
		, actor_(NULL)
		, owner_(NULL)
		, event_handler_(handler)
		, host_("")
		, port_(0)
		, error_string_("")
		, error_code_(0)
		, fatal_error_(false)
	{

	}

	TcpSocket::TcpSocket(::boost::scoped_ptr<SocketEventHandler>& handler)
		: id_(0)
		, sock_(-1)
		, bev_(NULL)
		, actor_(NULL)
		, owner_(NULL)
		, event_handler_(NULL)
		, host_("")
		, port_(0)
		, error_string_("")
		, error_code_(0)
		, fatal_error_(false)
	{
		event_handler_.swap(handler);
	}

	TcpSocket::~TcpSocket(void)
	{
		do_close();
		if (sock_ != -1) {
			CloseSocket(sock_);
		}
	}

	bool TcpSocket::Open(evutil_socket_t sock, struct event_base* base, 
		struct sockaddr* sa, int socklen)
	{
		assert(sock != -1);
		sock_ = sock;

		do
		{
			struct sockaddr_in* addr = (struct sockaddr_in*)(sa);
			host_ = std::string(::inet_ntoa(addr->sin_addr));
			port_ = int(::ntohs(addr->sin_port));

			bev_ = bufferevent_socket_new(base, sock_, BEV_OPT_CLOSE_ON_FREE);
			if (!bev_) {
				break;
			}
			bufferevent_setcb(bev_, conn_readcb, conn_writecb, conn_eventcb, this);
			bufferevent_disable(bev_, EV_WRITE);
			bufferevent_enable(bev_, EV_READ);

			event_handler_->tcp_socket_ = this;
			return true;
		} while (false);

		return false;
	}

	void TcpSocket::Close(void)
	{
		do_close();
		event_handler_->OnClose();
		owner_->do_close_socket(this);
	}

	void TcpSocket::Send(const ::std::uint8_t* buf, ::std::size_t size)
	{
		if (!fatal_error_)
		{
			bufferevent_enable(bev_, EV_WRITE);
			if (bufferevent_write(bev_, buf, size) != 0) {
				on_eof_or_error();
			}
		}
	}

	::std::size_t TcpSocket::Recv(::std::uint8_t* buf, ::std::size_t size)
	{
		return evbuffer_remove(bufferevent_get_input(bev_), buf, size);
	}

	::std::size_t TcpSocket::RecvLength(void) const
	{
		return evbuffer_get_length(bufferevent_get_input(bev_));
	}

	::std::size_t TcpSocket::RecvPeek(::std::uint8_t* buf, ::std::size_t size)
	{
		return evbuffer_copyout(bufferevent_get_input(bev_), buf, size);
	}

	::std::size_t TcpSocket::RecvSkip(::std::size_t size)
	{
		if (evbuffer_drain(bufferevent_get_input(bev_), size) == 0)
			return size;
		return 0;
	}

	void TcpSocket::CloseSocket(evutil_socket_t sock)
	{
#if defined(__WINDOWS__)
		::closesocket(sock);
#else
#endif
	}

	void TcpSocket::do_close(void)
	{
		if (bev_)
		{
			bufferevent_free(bev_);
			bev_ = NULL;
			sock_ = -1;
		}
	}

	void TcpSocket::on_eof_or_error(void)
	{
		if (fatal_error_) {
			return;
		}

		bufferevent_disable(bev_, EV_WRITE);
		fatal_error_ = true;
	}

	void TcpSocket::conn_readcb(struct ::bufferevent *, void *user_data)
	{
		TcpSocket* sock_ptr = static_cast<TcpSocket*>(user_data);
		assert(sock_ptr);
		sock_ptr->event_handler_->OnRead();
	}

	void TcpSocket::conn_writecb(struct bufferevent *, void *user_data)
	{
		TcpSocket* sock_ptr = static_cast<TcpSocket*>(user_data);
		assert(sock_ptr);

		if (!sock_ptr->fatal_error_)
		{            
			bufferevent_disable(sock_ptr->bev_, EV_WRITE);
			sock_ptr->event_handler_->OnWrite();
		}
	}

	void TcpSocket::conn_eventcb(struct bufferevent *, short events, void *user_data)
	{
		TcpSocket* sock_ptr = static_cast<TcpSocket*>(user_data);
		assert(sock_ptr);

		if (events & BEV_EVENT_EOF) {
			sock_ptr->on_eof_or_error();
			sock_ptr->event_handler_->OnEOF();
		}
		else if (events & BEV_EVENT_ERROR) {
			sock_ptr->error_code_ = errno;
#if defined(__WINDOWS__)
            char err_msg[1024] = { 0 };
            strerror_s(err_msg, sizeof(err_msg), errno);
            sock_ptr->error_string_ = err_msg;
#else
            sock_ptr->error_string_ = strerror(errno);
#endif
			sock_ptr->on_eof_or_error();
			sock_ptr->event_handler_->OnError();
		}
		else if (events & BEV_EVENT_TIMEOUT) {
			sock_ptr->event_handler_->OnTimeout();
		}
	}

}}  // namespace papa::net